/*
 * Copyright 2024-2025 the original author or authors.
 */

package io.modelcontextprotocol.sdk;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Random;

/**
 * Mock weather service that provides simulated weather data without requiring external
 * API calls. This is useful for development, testing, and demonstration purposes.
 *
 * @author MCP Java SDK Example
 */
public class MockWeatherService {

	private static final Logger logger = LoggerFactory.getLogger(MockWeatherService.class);

	private final Random random = new Random();

	// Mock weather conditions
	private static final String[] WEATHER_CONDITIONS = { "Clear", "Partly Cloudy", "Cloudy", "Overcast", "Light Rain",
			"Rain", "Heavy Rain", "Snow", "Light Snow", "Fog", "Mist" };

	private static final String[] WEATHER_DESCRIPTIONS = { "clear sky", "few clouds", "scattered clouds",
			"broken clouds", "overcast clouds", "light rain", "moderate rain", "heavy intensity rain", "light snow",
			"snow", "mist", "fog" };

	// Mock city data with coordinates
	private static final Map<String, CityInfo> MOCK_CITIES = Map.of("beijing",
			new CityInfo("Beijing", "CN", 39.9042, 116.4074), "shanghai",
			new CityInfo("Shanghai", "CN", 31.2304, 121.4737), "guangzhou",
			new CityInfo("Guangzhou", "CN", 23.1291, 113.2644), "london",
			new CityInfo("London", "GB", 51.5074, -0.1278), "newyork",
			new CityInfo("New York", "US", 40.7128, -74.0060), "tokyo", new CityInfo("Tokyo", "JP", 35.6762, 139.6503),
			"paris", new CityInfo("Paris", "FR", 48.8566, 2.3522), "sydney",
			new CityInfo("Sydney", "AU", -33.8688, 151.2093), "moscow", new CityInfo("Moscow", "RU", 55.7558, 37.6176),
			"berlin", new CityInfo("Berlin", "DE", 52.5200, 13.4050));

	public MockWeatherService() {
		logger.info("Using MockWeatherService - no API key required");
	}

	/**
	 * Get current weather for a city using mock data.
	 * @param city City name (e.g., "London", "New York", "Beijing")
	 * @param countryCode Optional country code (ignored in mock implementation)
	 * @return Mock weather information
	 * @throws WeatherServiceException if city is not found in mock data
	 */
	public WeatherInfo getCurrentWeather(String city, String countryCode) throws WeatherServiceException {
		logger.info("Getting mock weather data for city: {}", city);

		CityInfo cityInfo = findCity(city);
		if (cityInfo == null) {
			throw new WeatherServiceException("City not found in mock data: " + city);
		}

		// Generate mock weather data
		double temperature = generateTemperature();
		double feelsLike = temperature + random.nextGaussian() * 2; // Feels like can
																	// differ by ±2°C
		int humidity = 30 + random.nextInt(70); // 30-99%
		double pressure = 1000 + random.nextGaussian() * 20; // Around 1000 hPa ±20
		String condition = WEATHER_CONDITIONS[random.nextInt(WEATHER_CONDITIONS.length)];
		String description = WEATHER_DESCRIPTIONS[random.nextInt(WEATHER_DESCRIPTIONS.length)];
		double windSpeed = random.nextDouble() * 15; // 0-15 m/s
		double visibility = 5 + random.nextDouble() * 15; // 5-20 km

		return new WeatherInfo(cityInfo.name, cityInfo.country, Math.round(temperature * 10) / 10.0,
				Math.round(feelsLike * 10) / 10.0, humidity, Math.round(pressure * 10) / 10.0, condition, description,
				Math.round(windSpeed * 10) / 10.0, Math.round(visibility * 10) / 10.0);
	}

	/**
	 * Get weather forecast for a city using mock data.
	 * @param city City name
	 * @param countryCode Optional country code (ignored in mock implementation)
	 * @param days Number of days (1-5)
	 * @return Mock weather forecast
	 * @throws WeatherServiceException if city is not found or days is invalid
	 */
	public WeatherForecast getWeatherForecast(String city, String countryCode, int days)
			throws WeatherServiceException {
		if (days < 1 || days > 5) {
			throw new IllegalArgumentException("Days must be between 1 and 5");
		}

		logger.info("Getting mock forecast data for city: {}, days: {}", city, days);

		CityInfo cityInfo = findCity(city);
		if (cityInfo == null) {
			throw new WeatherServiceException("City not found in mock data: " + city);
		}

		List<ForecastItem> forecastItems = new ArrayList<>();

		// Generate forecast data for the requested number of days
		// Each day has 8 forecasts (3-hour intervals)
		for (int day = 0; day < days; day++) {
			for (int hour = 0; hour < 24; hour += 3) {
				String dateTime = String.format("2024-12-0%d %02d:00:00", (day + 1), hour);

				double baseTemp = generateTemperature();
				// Add some daily variation
				double tempVariation = Math.sin((hour - 6) * Math.PI / 12) * 8; // Peak at
																				// 18:00,
																				// low at
																				// 06:00
				double temperature = baseTemp + tempVariation;
				double feelsLike = temperature + random.nextGaussian() * 1.5;
				int humidity = 40 + random.nextInt(50);
				String condition = WEATHER_CONDITIONS[random.nextInt(WEATHER_CONDITIONS.length)];
				String description = WEATHER_DESCRIPTIONS[random.nextInt(WEATHER_DESCRIPTIONS.length)];
				double windSpeed = random.nextDouble() * 12;

				ForecastItem item = new ForecastItem();
				item.dateTime = dateTime;
				item.main = new Main();
				item.main.temp = Math.round(temperature * 10) / 10.0;
				item.main.feelsLike = Math.round(feelsLike * 10) / 10.0;
				item.main.humidity = humidity;
				item.weather = new Weather[] { new Weather() };
				item.weather[0].main = condition;
				item.weather[0].description = description;
				item.wind = new Wind();
				item.wind.speed = Math.round(windSpeed * 10) / 10.0;

				forecastItems.add(item);
			}
		}

		return new WeatherForecast(cityInfo.name, cityInfo.country, forecastItems.toArray(new ForecastItem[0]));
	}

	private CityInfo findCity(String city) {
		String cityKey = city.toLowerCase().replace(" ", "").replace("-", "");
		return MOCK_CITIES.get(cityKey);
	}

	private double generateTemperature() {
		// Generate temperature based on season (assuming winter for demo)
		// Range from -10°C to 25°C with normal distribution around 10°C
		return 10 + random.nextGaussian() * 8;
	}

	private static class CityInfo {

		final String name;

		final String country;

		final double lat;

		final double lon;

		CityInfo(String name, String country, double lat, double lon) {
			this.name = name;
			this.country = country;
			this.lat = lat;
			this.lon = lon;
		}

	}

	@JsonIgnoreProperties(ignoreUnknown = true)
	public static class Weather {

		public String main;

		public String description;

	}

	@JsonIgnoreProperties(ignoreUnknown = true)
	public static class Main {

		public double temp;

		@JsonProperty("feels_like")
		public double feelsLike;

		public int humidity;

		public double pressure;

	}

	@JsonIgnoreProperties(ignoreUnknown = true)
	public static class Wind {

		public double speed;

	}

	@JsonIgnoreProperties(ignoreUnknown = true)
	public static class ForecastItem {

		public Main main;

		public Weather[] weather;

		public Wind wind;

		@JsonProperty("dt_txt")
		public String dateTime;

	}

	public static class WeatherServiceException extends Exception {

		public WeatherServiceException(String message) {
			super(message);
		}

	}

}
